#pragma once
#ifndef TENSORITERATOR_H_
#define TENSORITERATOR_H_

#include "cmpMatCommon.h"
#include <type_traits>

namespace cmpMat
{
    template <typename _elementType>
    class __iterator_functions
    {
    public:
        typedef _elementType* iterator_type;
    private:
        typedef std::iterator_traits<iterator_type> __traits_type;
    public:
        typedef typename __traits_type::iterator_category iterator_category;
        typedef typename __traits_type::value_type value_type;
        typedef typename __traits_type::difference_type difference_type;
        typedef typename __traits_type::reference reference;
        typedef typename __traits_type::pointer pointer;

        virtual void add(const _elementType*& _m, const difference_type& __n) const = 0;
        void add(_elementType*& _m, const difference_type& __n) const
        { add(const_cast<const _elementType*&>(_m), __n); }
        virtual void sub(const _elementType*& _m, const difference_type& __n) const = 0;
        bool equal(const _elementType* const lhs, const _elementType* const rhs, const __iterator_functions& other) const
        { if (lhs == rhs && gethash() == other.gethash()) return deepequal(other); else return false; }
    protected:
        virtual bool deepequal(const __iterator_functions& other) const = 0;
        virtual CmpMatPtr gethash() const = 0;
    };

    template <typename _elementType>
    class _TensorIterator :
        public std::iterator<std::input_iterator_tag, _elementType*>
    {
    public:
        typedef _elementType* iterator_type;
        typedef typename std::remove_const<_elementType>::type iterfuncs_type;
    private:
        iterator_type _m;
        const __iterator_functions<iterfuncs_type>& _iterfuncs;
        typedef std::iterator_traits<iterator_type> __traits_type;
    public:
        typedef typename __traits_type::iterator_category iterator_category;
        typedef typename __traits_type::value_type value_type;
        typedef typename __traits_type::difference_type difference_type;
        typedef typename __traits_type::reference reference;
        typedef typename __traits_type::pointer pointer;

        _TensorIterator(iterator_type p, const __iterator_functions<iterfuncs_type>& iter_funcs) :
        _m(p), _iterfuncs(iter_funcs) {}

        _TensorIterator(const _TensorIterator& __i)
            : _m(__i.base()), _iterfuncs(__i._iterfuncs) { }

        // non-const to const constructor
        template<typename _elem_type>
        _TensorIterator(const _TensorIterator<_elem_type>& __i,
            typename std::enable_if< 
                std::is_const<_elementType>::value && 
                std::is_same< typename std::remove_const<_elementType>::type , _elem_type >::value
            >* = 0)
            : _m(__i.__getm()), _iterfuncs(__i.__getiterfuncs()) { }

        reference
        operator[](const difference_type& __n) const
        {return tmp(*this); tmp+=__n; return *tmp;}

        _TensorIterator& operator++() {(*this)+=1; return *this;}
        _TensorIterator operator++(int)
        {_TensorIterator tmp(*this); operator++(); return tmp;}
        _TensorIterator& operator--() {(*this)-=1; return *this;}
        _TensorIterator operator--(int)
        {_TensorIterator tmp(*this); operator--(); return tmp;}

        _TensorIterator& operator+=(const difference_type& __n)
        {_iterfuncs.add(_m, __n); return *this;}
        _TensorIterator& operator-=(const difference_type& __n)
        {_iterfuncs.sub(_m, __n); return *this;}

        bool operator==(const _TensorIterator& rhs) const {return _iterfuncs.equal(_m, rhs._m, rhs._iterfuncs); }
        bool operator!=(const _TensorIterator& rhs) const {return !(rhs==*this);}

        // const to non-const comparison
        template<typename _elem_type>
        typename std::enable_if< 
            (std::is_const<_elementType>::value && 
            std::is_same< typename std::remove_const<_elementType>::type , _elem_type >::value) ||
            (std::is_const<_elem_type>::value && 
            std::is_same< typename std::remove_const<_elem_type>::type , _elementType>::value),
        bool>::type
        operator==(const _TensorIterator<_elem_type>& rhs) const
        { return _iterfuncs.equal(__getm(), rhs.__getm(), rhs.__getiterfuncs()); }
        
        template<typename _elem_type>
        typename std::enable_if< 
            (std::is_const<_elementType>::value && 
            std::is_same< typename std::remove_const<_elementType>::type , _elem_type >::value) ||
            (std::is_const<_elem_type>::value && 
            std::is_same< typename std::remove_const<_elem_type>::type , _elementType>::value),
        bool>::type
        operator!=(const _TensorIterator<_elem_type>& rhs) const
        { return !(*this == rhs); }

        reference operator*() {return *_m;}
        pointer operator->() {return _m;}

        const iterator_type& __getm() const
        { return _m; }

        const __iterator_functions<iterfuncs_type>& __getiterfuncs() const
        { return _iterfuncs; }

         
    };


}

#endif